﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Configuration.Provider;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Text;
using System.Web.Security;

namespace BlogEngine.SQLServer
{
    public class LinqRoleProvider : RoleProvider
    {
        private string connString;
        private string tablePrefix;
        private string schema;
        private string applicationName;
        private bool isMultiBlog = false;

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            if (config == null)
            {
                throw new ArgumentNullException("config");
            }

            if (String.IsNullOrEmpty(name))
            {
                name = "LinqRoleProvider";
            }

            if (Type.GetType("Mono.Runtime") != null)
            {
                // Mono dies with a "Unrecognized attribute: description" if a description is part of the config.
                if (!string.IsNullOrEmpty(config["description"]))
                {
                    config.Remove("description");
                }
            }
            else
            {
                if (string.IsNullOrEmpty(config["description"]))
                {
                    config.Remove("description");
                    config.Add("description", "SQL Server Role Provider");
                }
            }

            base.Initialize(name, config);

            // Connection String
            if (config["connectionStringName"] == null)
            {
                config["connectionStringName"] = "BlogEngine";
            }
            connString = ConfigurationManager.ConnectionStrings[config["connectionStringName"]].ConnectionString;
            config.Remove("connectionStringName");

            // Table Prefix
            if (config["tablePrefix"] == null)
            {
                config["tablePrefix"] = "be_";
            }
            tablePrefix = config["tablePrefix"];
            config.Remove("tablePrefix");

            if (config["multiblog"] != null)
            {
                isMultiBlog = config["multiblog"].ToLower() == "true";
                config.Remove("multiblog");
            }

            if (config["schema"] == null)
            {
                config["schema"] = "dbo";
            }
            schema = config["schema"].Replace(".", "");
            config.Remove("schema");

            // Application Name
            if (config["applicationName"] == null)
            {
                config["applicationName"] = "BlogEngine";
            }
            applicationName = config["applicationName"];
            config.Remove("applicationName");

            // Throw an exception if unrecognized attributes remain
            if (config.Count > 0)
            {
                string attr = config.GetKey(0);
                if (!String.IsNullOrEmpty(attr))
                    throw new ProviderException("Unrecognized attribute: " + attr);
            } 
        }

        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            using (BlogData.BlogDataContext context = getNewContext())
            {
                DataLoadOptions options = new DataLoadOptions();
                options.LoadWith<BlogData.User>(u => u.UserRoles);
                context.LoadOptions = options;

                var users = from user in context.Users
                            where usernames.Contains(user.UserName)
                            select user;
                var roles = from role in context.Roles
                            where roleNames.Contains(role.RoleName)
                            select role;

                if (isMultiBlog)
                {
                    users = from user in users
                            join blogUser in context.BlogUsers on user.UserID equals blogUser.UserId
                            where blogUser.BlogId == Utils.GetBlogId()
                            select user;

                    roles = from role in roles
                            join blogRole in context.BlogRoles on role.RoleID equals blogRole.RoleId
                            where blogRole.BlogId == Utils.GetBlogId()
                            select role;
                }

                foreach (BlogData.User user in users)
                {
                    foreach (int roleId in roles.Select(r => r.RoleID))
                    {
                        if (user.UserRoles.Where(ur => ur.RoleID == roleId).Count() <= 0)
                        {
                            context.UserRoles.InsertOnSubmit(new BlogData.UserRole() { UserID = user.UserID, RoleID = roleId });
                        }
                    }
                }
                context.SubmitChanges();
            }
        }

        public override string ApplicationName
        {
            get
            {
                return applicationName;
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public override void CreateRole(string roleName)
        {
            using (BlogData.BlogDataContext context = getNewContext())
            {
                BlogData.Role role = new BlogData.Role() { RoleName = roleName };
                context.Roles.InsertOnSubmit(role);
                context.SubmitChanges();
                if (isMultiBlog)
                {
                    context.BlogRoles.InsertOnSubmit(new BlogData.BlogRole() { RoleId = role.RoleID, BlogId = Utils.GetBlogId() });
                    context.SubmitChanges();
                }
            }
        }

        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            if (roleName == "Administrators")
                return false;

            using (BlogData.BlogDataContext context = getNewContext())
            {
                var roles = from role in context.Roles
                            where role.RoleName == roleName
                            select role;
                if (isMultiBlog)
                {
                    roles = from role in roles
                            join blogRole in context.BlogRoles on role.RoleID equals blogRole.RoleId
                            where blogRole.BlogId == Utils.GetBlogId()
                            select role;
                }
                context.Roles.DeleteAllOnSubmit(roles);
                context.SubmitChanges();
                return true;
            }
        }

        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            using (BlogData.BlogDataContext context = getNewContext())
            {
                var users = from role in context.Roles
                            join userRole in context.UserRoles on role.RoleID equals userRole.RoleID
                            join user in context.Users on userRole.UserID equals user.UserID
                            where user.UserName.Contains(usernameToMatch) && role.RoleName == roleName
                            select user;
                if (isMultiBlog)
                {
                    users = from user in users
                            join blogUser in context.BlogUsers on user.UserID equals blogUser.UserId
                            where blogUser.BlogId == Utils.GetBlogId()
                            select user;
                }
                return users.Select(u => u.UserName).ToArray();
            }
        }

        public override string[] GetAllRoles()
        {
            using (BlogData.BlogDataContext context = getNewContext())
            {
                var roles = from role in context.Roles
                            select role;
                if (isMultiBlog)
                {
                    roles = from role in roles
                            join blogRole in context.BlogRoles on role.RoleID equals blogRole.RoleId
                            where blogRole.BlogId == Utils.GetBlogId()
                            select role;
                }
                return roles.Select(r => r.RoleName).ToArray();
            }
        }

        public override string[] GetRolesForUser(string username)
        {
            using (BlogData.BlogDataContext context = getNewContext())
            {
                var roles = from role in context.Roles
                            join userRole in context.UserRoles on role.RoleID equals userRole.RoleID
                            join user in context.Users on userRole.UserID equals user.UserID
                            where user.UserName == username
                            select role;
                if (isMultiBlog)
                {
                    roles = from role in roles
                            join blogRoles in context.BlogRoles on role.RoleID equals blogRoles.RoleId
                            where blogRoles.BlogId == Utils.GetBlogId()
                            select role;
                }
                return roles.Select(r => r.RoleName).ToArray();
            }
        }

        public override string[] GetUsersInRole(string roleName)
        {
            using (BlogData.BlogDataContext context = getNewContext())
            {
                var users = from user in context.Users
                            join userRole in context.UserRoles on user.UserID equals userRole.UserID
                            join role in context.Roles on userRole.RoleID equals role.RoleID
                            where role.RoleName == roleName
                            select user;
                if (isMultiBlog)
                {
                    users = from user in users
                            join blogUser in context.BlogUsers on user.UserID equals blogUser.UserId
                            where blogUser.BlogId == Utils.GetBlogId()
                            select user;
                }
                return users.Select(u => u.UserName).ToArray();
            }
        }

        public override bool IsUserInRole(string username, string roleName)
        {
            BlogData.Role roleData = null;
            using (BlogData.BlogDataContext context = getNewContext())
            {
                var roles = from user in context.Users
                            join userRole in context.UserRoles on user.UserID equals userRole.UserID
                            join role in context.Roles on userRole.RoleID equals role.RoleID
                            where user.UserName == username && role.RoleName == roleName
                            select role;
                if (isMultiBlog)
                {
                    roles = from role in roles
                            join blogRole in context.BlogRoles on role.RoleID equals blogRole.RoleId
                            where blogRole.BlogId == Utils.GetBlogId()
                            select role;
                }
                roleData = roles.FirstOrDefault();
            }

            if (roleData != null)
            {
                return true;
            }
            return false;
        }

        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            using (BlogData.BlogDataContext context = getNewContext())
            {
                DataLoadOptions options = new DataLoadOptions();
                options.LoadWith<BlogData.User>(u => u.UserRoles);
                context.LoadOptions = options;

                var users = from user in context.Users
                            where usernames.Contains(user.UserName)
                            select user;
                List<int> roleIds = (from role in context.Roles
                                     where roleNames.Contains(role.RoleName)
                                     select role.RoleID).ToList();

                if (isMultiBlog)
                {
                    users = from user in users
                            join blogUser in context.BlogUsers on user.UserID equals blogUser.UserId
                            where blogUser.BlogId == Utils.GetBlogId()
                            select user;
                }
                foreach (BlogData.User user in users)
                {
                    context.UserRoles.DeleteAllOnSubmit(user.UserRoles.Where(ur => roleIds.Contains(ur.RoleID)));
                }
                context.SubmitChanges();
            }
        }

        public override bool RoleExists(string roleName)
        {
            using (BlogData.BlogDataContext context = getNewContext())
            {
                var roles = from role in context.Roles
                            where role.RoleName == roleName
                            select role;

                if (isMultiBlog)
                {
                    roles = from role in roles
                            join blogRole in context.BlogRoles on role.RoleID equals blogRole.RoleId
                            where blogRole.BlogId == Utils.GetBlogId()
                            select role;
                }
                BlogData.Role roleData = roles.FirstOrDefault();
                return roleData != null;
            }
        }

        private BlogData.BlogDataContext getNewContext()
        {
            return Utils.getNewMembershipContext(connString, tablePrefix, schema);
        }
    }
}
